﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;

/**
 *
 * author alaah
 */

namespace mlnx_os_sysdump_analyzer
{
    class FileAnalyzer : IDisposable
    {
        /// <summary>
        /// flag for controlling the threads
        /// </summary>
        public bool KEEP_RUNNING = true; 
        
        /// <summary>
        /// tmp folder path
        /// </summary>
        public string OUTPUT_DIR_PATH = System.IO.Path.GetTempPath()
            + "\\tmpDir_dump_analyzer_"
            + Regex.Replace(Regex.Replace(Regex.Replace(DateTime.Now.ToString(), " ", "_"), ":", "-"), "/", "-");
        
        /// <summary>
        /// flag that the view layer will use to check if the analyzes was completed.
        /// </summary>
        public Boolean isDone = false;
        
        /// <summary>
        /// flag which indicates if the user opened a sysdump file.
        /// </summary>
        public Boolean loadedLogsFile = false;
        
        /// <summary>
        /// the sysdump files will be deleted only if the input was a compressed file.
        /// otherwise, only the extracted messages files will be deleted.
        /// </summary>
        private bool toDeleteSysDumpFiles = false;
        
        /// <summary>
        /// dictionary that holds the content of the mstdump file
        /// </summary>
        Dictionary<string, string> mstdumpDictionary = null;

        /// <summary>
        /// the path of the input file/folder
        /// </summary>
        private string filePath = "";

        /// <summary>
        /// will parse the content of the mstdump.
        /// </summary>
        private Device dev = null;

        /// <summary>
        /// holds the system information (SW & hostname...) taken from the sysinfo.txt
        /// </summary>
        public string systemInfo = ""; // SW version and hostname....
        
        /// <summary>
        /// content of ufma_data.txt
        /// </summary>
        public string ufmaData = "";

        /// <summary>
        /// content of flint_sysapimlnxsw-255.log
        /// </summary>
        public string asicFlintLog = "";

        /// <summary>
        /// port Info Scanner.
        /// can be Eth or Ib port scanner.
        /// </summary>
        //public PortInfoInterface portInfoScanner = null;
        public PortInfo portInfoScanner = null;
        
        /// <summary>
        /// holds the list of all the threads
        /// </summary>
        public ArrayList threadsList;

        /// <summary>
        /// lspci Items
        /// </summary>
        public ArrayList lspciItems;

        /// <summary>
        /// config DB Content
        /// </summary>
        public ArrayList configDBContent;

        public Messages messagesScanner;        
        public Dmesg dmesgScanner;
        public ChassisInfo ChassisInfoScanner;
        public CommandHistory rootCMDHistory;
        public CommandHistory monitorCMDHistory;
        public EthInfo ethInfoScanner;
        public RunningConfig runningConfigScanner;
        public SwitchConfigurations switchConfigurationsScanner;
        public FilesList filesListScanner;
        public IssDump issDumpScanner;
        public Processes processesScanner;
        public OpenSMConf openSMScanner;
        public Systemlog syslogScanner;
        //
        //----------------------------------------------------
        /// <summary>
        /// Constructor.
        /// Initalizes the file path
        /// </summary>
        /// <param name="filePath">path to the sysdump file (or directory after extraction) or mstdump file.</param>
        public FileAnalyzer(string filePath)
        {
            this.filePath = filePath;
        }
        //
        //----------------------------------------------------
        /// <summary>
        /// scan the content of the provided file.
        /// temporary files will be deleted at the end of this function.
        /// each file will be scanned in a separate thread, the function will wait for all the threads.
        /// </summary>
        public void loadFile()
        {
            /// check the input
            if (filePath == "" || (!File.Exists(filePath) && !Directory.Exists(filePath)))
            {
                isDone = true;
                return;
            }
            /// init the flags
            KEEP_RUNNING = true;
            isDone = false;
            toDeleteSysDumpFiles = false;

            OUTPUT_DIR_PATH = System.IO.Path.GetTempPath()
                + "\\tmp_sysdump_"
                + Path.GetRandomFileName()
                + Regex.Replace(Regex.Replace(Regex.Replace(DateTime.Now.ToString(), " ", "_"), ":", "-"), "/", "-");
                        
            // if it's a compressed file (with "tgz" extention)
            // or if it's a decompressed sysdump file.
            if (filePath.EndsWith("tgz") || Directory.Exists(filePath))
            {
                loadedLogsFile = true;
                if (Directory.Exists(filePath))
                {
                    OUTPUT_DIR_PATH = filePath;
                }
                else
                {
                    /// extract the file to a tmp folder
                    if (Methods.ExtractTarFile(filePath, OUTPUT_DIR_PATH))
                    {
                        toDeleteSysDumpFiles = true;
                    }
                    else
                    { /// Could not decompress archive
                        isDone = true;
                        return;
                    }
                }
                /// init controls
                messagesScanner = new Messages(ref this.KEEP_RUNNING, ref OUTPUT_DIR_PATH);
                dmesgScanner = new Dmesg(ref this.KEEP_RUNNING, ref OUTPUT_DIR_PATH);
                ChassisInfoScanner = new ChassisInfo(ref this.KEEP_RUNNING);
                threadsList = new ArrayList();
                lspciItems = new ArrayList();
                configDBContent = new ArrayList();
                rootCMDHistory = new CommandHistory(ref this.KEEP_RUNNING, ref OUTPUT_DIR_PATH, "root");
                monitorCMDHistory = new CommandHistory(ref this.KEEP_RUNNING, ref OUTPUT_DIR_PATH, "monitor");
                ethInfoScanner = new EthInfo(ref this.KEEP_RUNNING, ref OUTPUT_DIR_PATH);
                runningConfigScanner = new RunningConfig(ref this.KEEP_RUNNING, ref OUTPUT_DIR_PATH);
                switchConfigurationsScanner = new SwitchConfigurations(ref this.KEEP_RUNNING, ref OUTPUT_DIR_PATH);
                filesListScanner = new FilesList(ref this.KEEP_RUNNING, ref OUTPUT_DIR_PATH);
                issDumpScanner = new IssDump(ref this.KEEP_RUNNING, ref OUTPUT_DIR_PATH);
                processesScanner = new Processes(ref this.KEEP_RUNNING, ref OUTPUT_DIR_PATH);
                openSMScanner = new OpenSMConf(ref this.KEEP_RUNNING, ref OUTPUT_DIR_PATH);
                syslogScanner = new Systemlog(ref this.KEEP_RUNNING, ref OUTPUT_DIR_PATH);
                /// ---- 

                /// get the mstdump
                /// -- in Eth switches, from versoin 3.2.0500 the mstdump files will be in 3 file ()
                /// -- in IB and Eth (version less than 3.2.0500 they will be in the sysinfo.txt file.
                string sysInfoFilePath = Methods.FindFileRec(OUTPUT_DIR_PATH, "sysinfo.txt");
                string mstdumpFilePath = Methods.FindFileRec(OUTPUT_DIR_PATH, "mstdump1");
                if (mstdumpFilePath == null)
                {
                    mstdumpFilePath = sysInfoFilePath;
                }
                threadsList.Add(new Thread(new ParameterizedThreadStart(loadMstdumpContent)));
                ((Thread)threadsList[threadsList.Count - 1]).Start(mstdumpFilePath);

                /// scan SW version and hostname....
                threadsList.Add(new Thread(new ParameterizedThreadStart(scanForGeneralInfo)));
                ((Thread)threadsList[threadsList.Count - 1]).Start(sysInfoFilePath);

                //#################
                // get port info
				portInfoScanner = new PortInfo(ref this.KEEP_RUNNING, ref OUTPUT_DIR_PATH);
				threadsList.Add(new Thread(new ParameterizedThreadStart(portInfoScanner.scanPortInfo)));
                ((Thread)threadsList[threadsList.Count - 1]).Start(sysInfoFilePath);
                //#################
                //
                threadsList.Add(new Thread(new ParameterizedThreadStart(ChassisInfoScanner.scanHardwarInfo)));
                ((Thread)threadsList[threadsList.Count - 1]).Start(sysInfoFilePath);
                //
                threadsList.Add(new Thread(new ThreadStart(rootCMDHistory.scanInfo)));
                ((Thread)threadsList[threadsList.Count - 1]).Start();
                //
                threadsList.Add(new Thread(new ThreadStart(monitorCMDHistory.scanInfo)));
                ((Thread)threadsList[threadsList.Count - 1]).Start();
                //
                threadsList.Add(new Thread(new ThreadStart(switchConfigurationsScanner.scanInfo)));
                ((Thread)threadsList[threadsList.Count - 1]).Start();
                //
                threadsList.Add(new Thread(new ThreadStart(scanLspci)));
                ((Thread)threadsList[threadsList.Count - 1]).Start();
                //
                threadsList.Add(new Thread(new ThreadStart(scanUfmaData)));
                ((Thread)threadsList[threadsList.Count - 1]).Start();
                // scan for errors
                threadsList.Add(new Thread(new ThreadStart(messagesScanner.scanForErrors)));
                ((Thread)threadsList[threadsList.Count - 1]).Start();
                //
                threadsList.Add(new Thread(new ThreadStart(dmesgScanner.scanInfo)));
                ((Thread)threadsList[threadsList.Count - 1]).Start();
                //
                threadsList.Add(new Thread(new ThreadStart(ethInfoScanner.scanInfo)));
                ((Thread)threadsList[threadsList.Count - 1]).Start();
                //
                threadsList.Add(new Thread(new ThreadStart(runningConfigScanner.scanInfo)));
                ((Thread)threadsList[threadsList.Count - 1]).Start();
                //
                threadsList.Add(new Thread(new ThreadStart(filesListScanner.scanInfo)));
                ((Thread)threadsList[threadsList.Count - 1]).Start();
                //
                threadsList.Add(new Thread(new ThreadStart(scanConfigDBContent)));
                ((Thread)threadsList[threadsList.Count - 1]).Start();
                //                    
                threadsList.Add(new Thread(new ThreadStart(issDumpScanner.scanInfo)));
                ((Thread)threadsList[threadsList.Count - 1]).Start();
                //                    
                threadsList.Add(new Thread(new ThreadStart(processesScanner.scanProcessesInfoNewThread)));
                ((Thread)threadsList[threadsList.Count - 1]).Start();
                //
                threadsList.Add(new Thread(new ThreadStart(openSMScanner.scanInfo)));
                ((Thread)threadsList[threadsList.Count - 1]).Start();
                //
                threadsList.Add(new Thread(new ThreadStart(getAsicFlintLog)));
                ((Thread)threadsList[threadsList.Count - 1]).Start();
                //
                threadsList.Add(new Thread(new ThreadStart(syslogScanner.scanInfo)));
                ((Thread)threadsList[threadsList.Count - 1]).Start();
                //
                //#################
                //
                // wait for the threads to finish scanning.
                waitForThreads();
                //
                // determine switch family and protocol type
                //
                SWFamily swFamily;
                if (systemInfo.Contains("SX"))
                {
                    swFamily = SWFamily.SX;
                }
                else
                {
                    if (systemInfo.Contains("EFM"))
                    {
                        swFamily = SWFamily.FIT;
                    }
                    else
                    {
                        swFamily = SWFamily.NA;
                    }
                }
                SWProfile swProfile;
                if (portInfoScanner.getIBPortsTable().Rows.Count == 0 && portInfoScanner.getETHPortsTable().Rows.Count != 0)
                {
                    // ETH
                    swProfile = SWProfile.ETH;
                }
                else
                {
                    if (portInfoScanner.getIBPortsTable().Rows.Count != 0 && portInfoScanner.getETHPortsTable().Rows.Count == 0)
                    {
                        // IB
                        swProfile = SWProfile.IB;
                    }
                    else
                    {
                        if (portInfoScanner.getIBPortsTable().Rows.Count != 0 && portInfoScanner.getETHPortsTable().Rows.Count != 0)
                        {
                            // VPI
                            swProfile = SWProfile.VPI;
                        }
                        else
                        {
                            // NA
                            swProfile = SWProfile.NA;
                        }
                    }
                }
                // scan the daemons status according to the local role.
                messagesScanner.scanDaemonsStatus(ChassisInfoScanner.localRole, swProfile, swFamily);
                // ******
                // delete all temp file
                clearTmpFiles();
            }
            else /// if it's not a compressed file
            {
                loadedLogsFile = false;
                /// try to get mstdump info
                loadMstdumpContent(filePath);
            }
            ///
            if (mstdumpDictionary!= null && mstdumpDictionary.Count > 10)
            {
                dev = new Device();
                dev.identifyDevice(mstdumpDictionary);
            }
            /// tell the view layer that the control layer is done.
            isDone = true;
        }
        //
        //----------------------------------------------------
        /// <summary>
        /// get the information retreived from the mstdump
        /// </summary>
        /// <returns></returns>
        public string getMstdumpInfo()
        {
            if (dev != null)
            {
                return dev.getInfo();
            }
            //return "Error: The provided file does not contain a valid mstdump output!";
            return "Error: The provided file is not supported!"
                + "\r\n\tOnly mstdump output, MLNX-OS and FabricIT EFM sysdump files (*.tgz files) are supported.";
        }
        //
        //----------------------------------------------------
        /// <summary>
        /// load the content of mstdump
        /// </summary>
        /// <param name="param">path of the mstdump file</param>
        private void loadMstdumpContent(object param)
        {
            string srcFilePath = (string)param;
            /// check the input
            if (srcFilePath == null)
                return;
            StreamReader sr = new StreamReader(srcFilePath);
            /// init the dictionary
            mstdumpDictionary = new Dictionary<string, string>();
            try
            {
                while (KEEP_RUNNING && sr.Peek() >= 0)
                {
                    /// read line
                    string line = sr.ReadLine();
                    try
                    {
                        /// check that the line has two hex numbers
                        if (line.Contains("0x") && Regex.Split(line, " ").Length == 2)
                        {
                            /// remove the "0x"
                            line = Regex.Replace(line, "0x", "");
                            /// get the address and the value
                            string[] addrAndValue = Regex.Split(line, " ");
                            /// remove the leading zeros from left
                            addrAndValue[0] = addrAndValue[0].TrimStart('0'); addrAndValue[0] = (addrAndValue[0].Equals("")) ? "0" : addrAndValue[0];
                            addrAndValue[1] = addrAndValue[1].TrimStart('0'); addrAndValue[1] = (addrAndValue[1].Equals("")) ? "0" : addrAndValue[1];
                            /// add to dictionary
                            mstdumpDictionary.Add(addrAndValue[0], addrAndValue[1]);
                        }
                    }
                    catch
                    { }
                }
            }
            catch
            { }
            sr.Dispose();
        }
        //
        //----------------------------------------------------
        /// <summary>
        /// get the system informaion
        /// </summary>
        /// <param name="param">sysinfo.txt file path</param>
        public void scanForGeneralInfo(object param)
        {
            string srcFilePath = (string)param;
            if (srcFilePath == null)
                return;
            StreamReader sr = new StreamReader(srcFilePath);
            try
            {
                while (KEEP_RUNNING && sr.Peek() >= 0)
                {
                    // read line
                    string line = sr.ReadLine();
                    try
                    {
                        if (line.Contains("System information:"))
                        {
                            sr.ReadLine(); // skip
                            systemInfo += sr.ReadLine() + "\r\n";
                            systemInfo += sr.ReadLine() + "\r\n";
                            systemInfo += sr.ReadLine() + "\r\n";
                            systemInfo += sr.ReadLine() + "\r\n";
                        }
                    }
                    catch
                    { }
                }
            }
            catch
            { }
            sr.Dispose();
        }
        //
        //----------------------------------------------------
        /// <summary>
        /// delete all the created temp files.
        /// </summary>
        public void clearTmpFiles()
        {
            /// delete the extracted files
            if (Directory.Exists(OUTPUT_DIR_PATH))
            {
                if (toDeleteSysDumpFiles)
                {
                    /// delete all tmp files
                    Methods.DeleteFolderRec(OUTPUT_DIR_PATH);
                }
                else
                {
                    /// delete only the extracted messages files.
                    Methods.DeleteFolderRec(OUTPUT_DIR_PATH + "\\messages_tmp");
                }
            }
        }
        //
        //----------------------------------------------------
        /// <summary>
        /// wait for the threads to finish
        /// </summary>
        private void waitForThreads()
        {
            for (int i = 0; i < threadsList.Count; i++)
            {
                ((Thread)threadsList[i]).Join();
            }
        }
        //
        //----------------------------------------------------
        /// <summary>
        /// scan the content of lspci-vvv.log
        /// </summary>
        private void scanLspci()
        {
            string path = Methods.FindFileRec(OUTPUT_DIR_PATH, "lspci-vvv.log");
            if (path != null)
            {
                /// get the content of the file.
                String tmp = Methods.GetFileContent(path, ref KEEP_RUNNING);
                try
                {
                    String[] lines = Regex.Split(tmp, "\n");
                    Item tmpItem = null;
                    /// scan all the lines
                    for (int i = 0; i < lines.Length; i++)
                    {
                        /// does this line contains a new devices?
                        if (Regex.Match(lines[i], "[0-9]*:[0-9]*:[0-9]*.[0-9]*").Success)
                        {
                            if (tmpItem != null)
                            {
                                lspciItems.Add(tmpItem);
                            }
                            tmpItem = new Item();
                            tmpItem.itemName = lines[i];
                            tmpItem.content = lines[i] + "\n\n";
                        }
                        else
                        {
                            if (tmpItem != null)
                            {
                                /// add to the current device.
                                tmpItem.content += lines[i] + "\n";
                            }
                        }
                    }
                    /// add the last one...
                    if (tmpItem != null)
                    {
                        lspciItems.Add(tmpItem);
                    }
                }
                catch
                { }
            }
            else
            {
                lspciItems.Add(new Item("Error", "Couldn't locate the file: lspci-vvv.log"));
            }
        }
        //
        //----------------------------------------------------
        /// <summary>
        /// scan the content of ufma_data.txt
        /// </summary>
        private void scanUfmaData()
        {
            /// get the content of the file.
            string path = Methods.FindFileRec(OUTPUT_DIR_PATH, "ufma_data.txt");
            if (path != null)
            {
                ufmaData = Methods.GetFileContent(path, ref KEEP_RUNNING);
            }
            else
            {
                ufmaData = "Couldn't locate the file: ufma_data.txt";
            }
        }
        //
        //----------------------------------------------------
        /// <summary>
        /// scan the files under /config/db folder
        /// </summary>
        private void scanConfigDBContent()
        {
            /// get a list of files under the /confing/db directory
            ArrayList al = Methods.GetFileListInDir(OUTPUT_DIR_PATH, "db");
            if (al != null)
            {
                foreach (Item itm in al)
                {
                    /// add the file name and content to the list
                    configDBContent.Add(new Item(itm.itemName, Regex.Replace(Methods.GetFileContent(itm.content, ref KEEP_RUNNING), "\0", "")));
                }
            }
        }
        //
        //----------------------------------------------------
        /// <summary>
        /// scan the content of flint_sysapimlnxsw-255.log
        /// </summary>
        private void getAsicFlintLog()
        {
            /// get the content of the file.
            string path = Methods.FindFileRec(OUTPUT_DIR_PATH, "flint_sysapimlnxsw-255.log");
            if (path != null)
            {
                asicFlintLog = Methods.GetFileContent(path, ref KEEP_RUNNING);
            }
            else
            {
                asicFlintLog = "Couldn't locate the file: flint_sysapimlnxsw-255.log";
            }
        }
        //
        //----------------------------------------------------
        //

        public void Dispose()
        {
            try
            {
                if (messagesScanner != null) messagesScanner.Dispose();
                if (rootCMDHistory != null) rootCMDHistory.Dispose();
                if (monitorCMDHistory != null) monitorCMDHistory.Dispose();
                if (switchConfigurationsScanner != null) switchConfigurationsScanner.Dispose();
                if (filesListScanner != null) filesListScanner.Dispose();
                if (issDumpScanner != null) issDumpScanner.Dispose();
                if (processesScanner != null) processesScanner.Dispose();
                if (openSMScanner != null) openSMScanner.Dispose();
                if (syslogScanner != null) syslogScanner.Dispose();
                GC.Collect();
            }
            catch { }
        }
    }
}
